package com.hqd.ch03.v21.aop.framework;

import com.hqd.ch03.v21.aop.Advised;
import com.hqd.ch03.v21.aop.support.AopUtils;
import com.hqd.ch03.v21.core.cglib.SpringImitationNamingPolicy;
import com.hqd.ch03.v21.utils.ClassUtils;
import lombok.AllArgsConstructor;
import net.sf.cglib.proxy.*;

import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.List;

public class CglibAopProxy implements AopProxy {
    /**
     * 规定callbacks数组顺序
     */
    private static final int AOP_PROXY = 0;//通知方法
    private static final int INVOKE_TARGET = 1;//目标对象方法
    private static final int NO_OVERRIDE = 2;//空操作
    private static final int DISPATCH_TARGET = 3;//获取目标类class对象
    private static final int DISPATCH_ADVISED = 4;//advised 相关的方法
    protected final AdvisedSupport advised;
    private final transient AdvisedDispatcher advisedDispatcher;
    protected Object[] constructorArgs;
    protected Class<?>[] constructorArgTypes;

    public CglibAopProxy(AdvisedSupport config) {
        this.advised = config;
        this.advisedDispatcher = new AdvisedDispatcher(this.advised);
    }

    private static Object invokeMethod(Object target, Method method, Object[] args, MethodProxy methodProxy)
            throws Throwable {
        return methodProxy.invoke(target, args);
    }

    private static Object processReturnType(
            Object proxy, Object target, Method method, Object returnValue) {

        // Massage return value if necessary
        if (returnValue != null && returnValue == target &&
                !RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
            returnValue = proxy;
        }
        Class<?> returnType = method.getReturnType();
        if (returnValue == null && returnType != Void.TYPE && returnType.isPrimitive()) {
            throw new RuntimeException(
                    "Null return value from advice does not match primitive return type for: " + method);
        }
        return returnValue;
    }

    @Override
    public Object getProxy() {
        return getProxy(null);
    }

    @Override
    public Object getProxy(ClassLoader classLoader) {
        Enhancer enhancer = new Enhancer();
        Class<?> rootClass = this.advised.getTargetClass();

        Class<?> proxySuperClass = rootClass;
        /**
         * 如果是代理类，则再往上获取父类
         */
        if (rootClass.getName().contains(ClassUtils.CGLIB_CLASS_SEPARATOR)) {
            proxySuperClass = rootClass.getSuperclass();
            Class<?>[] additionalInterfaces = rootClass.getInterfaces();
            for (Class<?> additionalInterface : additionalInterfaces) {
                this.advised.addInterface(additionalInterface);
            }
        }

        enhancer.setSuperclass(proxySuperClass);
        /**
         * 指定命名策略，防止冲突
         */
        enhancer.setNamingPolicy(SpringImitationNamingPolicy.INSTANCE);
        enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));
        if (classLoader != null) {
            enhancer.setClassLoader(classLoader);
        }
        /**
         * 添加默认拦截器，执行增强
         */

        Callback[] callbacks = getCallbacks(rootClass);
        Class<?>[] types = new Class<?>[callbacks.length];
        for (int x = 0; x < types.length; x++) {
            types[x] = callbacks[x].getClass();
        }
        enhancer.setCallbackFilter(new ProxyCallbackFilter(
                this.advised.getConfigurationOnlyCopy()));
        enhancer.setCallbacks(callbacks);
        enhancer.setCallbackTypes(types);

        /**
         * 创建代理对象
         */
        return createProxyClassAndInstance(enhancer, callbacks);
    }

    protected Object createProxyClassAndInstance(Enhancer enhancer, Callback[] callbacks) {
        enhancer.setInterceptDuringConstruction(false);
        enhancer.setCallbacks(callbacks);
        return (this.constructorArgs != null && this.constructorArgTypes != null ?
                enhancer.create(this.constructorArgTypes, this.constructorArgs) :
                enhancer.create());
    }

    private Callback[] getCallbacks(Class<?> rootClass) {
        boolean exposeProxy = this.advised.isExposeProxy();
        boolean isStatic = true;

        //1.切面增强调用拦截器
        Callback aopInterceptor = new DynamicAdvisedInterceptor(this.advised);

        Callback targetInterceptor;
        /**
         * 2.代理对象拦截器，是否暴露代理对象
         * 两个拦截器的唯一区别就是，是否暴露代理对象
         */
        if (exposeProxy) {
            targetInterceptor = new StaticUnadvisedExposedInterceptor(this.advised.getTarget());
        } else {
            targetInterceptor = new StaticUnadvisedInterceptor(this.advised.getTarget());
        }

        //4.序列化拦截器
        Callback targetDispatcher = new StaticDispatcher(this.advised.getTarget());

        Callback[] mainCallbacks = new Callback[]{
                aopInterceptor,
                targetInterceptor,
                new SerializableNoOp(),
                targetDispatcher, this.advisedDispatcher
        };
        return mainCallbacks;
    }

    public void setConstructorArguments(Object[] constructorArgs, Class<?>[] constructorArgTypes) {
        if (constructorArgs == null || constructorArgTypes == null) {
            throw new IllegalArgumentException("Both 'constructorArgs' and 'constructorArgTypes' need to be specified");
        }
        if (constructorArgs.length != constructorArgTypes.length) {
            throw new IllegalArgumentException("Number of 'constructorArgs' (" + constructorArgs.length +
                    ") must match number of 'constructorArgTypes' (" + constructorArgTypes.length + ")");
        }
        this.constructorArgs = constructorArgs;
        this.constructorArgTypes = constructorArgTypes;
    }

    private static class AdvisedDispatcher implements Dispatcher, Serializable {

        private final AdvisedSupport advised;

        public AdvisedDispatcher(AdvisedSupport advised) {
            this.advised = advised;
        }

        @Override
        public Object loadObject() {
            return this.advised;
        }
    }

    public static class SerializableNoOp implements NoOp, Serializable {
    }

    private static class StaticDispatcher implements Dispatcher, Serializable {

        private final Object target;

        public StaticDispatcher(Object target) {
            this.target = target;
        }

        @Override
        public Object loadObject() {
            return this.target;
        }
    }

    /**
     * 正常执行方法
     */
    private static class StaticUnadvisedInterceptor implements MethodInterceptor, Serializable {

        private final Object target;

        public StaticUnadvisedInterceptor(Object target) {
            this.target = target;
        }

        @Override
        public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
            Object retVal = invokeMethod(this.target, method, args, methodProxy);
            return processReturnType(proxy, this.target, method, retVal);
        }
    }


    /**
     * 暴露代理对象
     */
    private static class StaticUnadvisedExposedInterceptor implements MethodInterceptor, Serializable {

        private final Object target;

        public StaticUnadvisedExposedInterceptor(Object target) {
            this.target = target;
        }

        @Override
        public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
            Object oldProxy = null;
            try {
                oldProxy = AopContext.setCurrentProxy(proxy);
                Object retVal = invokeMethod(this.target, method, args, methodProxy);
                return processReturnType(proxy, this.target, method, retVal);
            } finally {
                AopContext.setCurrentProxy(oldProxy);
            }
        }
    }

    @AllArgsConstructor
    private static class DynamicAdvisedInterceptor implements MethodInterceptor, Serializable {
        private AdvisedSupport advised;


        @Override
        public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
            Class<?> targetClass = this.advised.getTargetClass();
            Object target = this.advised.getTarget();
            List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
            ReflectiveMethodInvocation rmi = new ReflectiveMethodInvocation(target, proxy,
                    method, args, chain.toArray(new Object[0]));
            return rmi.proceed();
        }
    }


    private static class ProxyCallbackFilter implements CallbackFilter {
        private final AdvisedSupport advised;

        public ProxyCallbackFilter(AdvisedSupport advised) {
            this.advised = advised;
        }

        /**
         * 这里返回值就是callbacks的下标
         * 也就是说，这里是去匹配对应的回调函数
         *
         * @return
         */

        @Override
        public int accept(Method method) {
            /**
             * 空操作
             */
            if (AopUtils.isFinalizeMethod(method)) {
                return NO_OVERRIDE;
            }
            /**
             * Advised相关的方法委托给ADVISED分发器
             */
            if (!this.advised.isOpaque() && method.getDeclaringClass().isInterface() &&
                    method.getDeclaringClass().isAssignableFrom(Advised.class)) {
                return DISPATCH_ADVISED;
            }
            Class<?> targetClass = this.advised.getTargetClass();
            List<?> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
            boolean haveAdvice = !chain.isEmpty();
            boolean exposeProxy = this.advised.isExposeProxy();
            boolean isFrozen = this.advised.isFrozen();
            /**
             * 方法存在通知，使用增强拦截器
             */
            if (haveAdvice || !isFrozen) {
                return AOP_PROXY;
            } else {//不存在，调用目标方法
                if (exposeProxy) {
                    return INVOKE_TARGET;
                }
                Class<?> returnType = method.getReturnType();
                if (targetClass != null && returnType.isAssignableFrom(targetClass)) {
                    return INVOKE_TARGET;
                } else {
                    return DISPATCH_TARGET;
                }
            }
        }
    }
}
